package tv.acgn.advertisement.publish.core;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

/**
 * @program: advertisement-ad-publish-core
 * @description: 自动事务管理拦截器
 * @author: junG
 * @create: 2019-05-01 15:37
 **/
@Configuration
@Slf4j
public class TxAdviceConfig {

    /**
     * 设置事务超时时间 为30秒
     */
    private static final int TX_METHOD_TIMEOUT = 30;
    /**
     * 设置 事务管理器 自动拦截点
     * 为包含所有 service 业务类方法
     * 其他类中的方法 需要事务 则需要自己配置 @Transactional 注解
     *
     * @see Transactional
     */
    private static final String AOP_POINTCUT_EXPRESSION = "execution(* tv.acgn.advertisement.publish.service.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    /**
     * @return org.springframework.transaction.interceptor.TransactionInterceptor
     *
     * @Author junG
     * @Description 配置事务管理拦截器 事务方法切面设定 包含事务行为 需要拦截的方法等
     * @Date 15:46 2019/5/1
     * @Param []
     */
    @Bean
    public TransactionInterceptor txAdvice() {
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();

        /**
         * 只读事务，不做更新操作
         * 不需要异常自动回滚 设定不启用事务传播 {@link TransactionDefinition.PROPAGATION_NOT_SUPPORTED}
         *
         */
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);

        /**
         * 变更事务 需要异常自动回滚
         * 定义事务传播行为
         * 使用 需要新事务等级 {@link TransactionDefinition.PROPAGATION_REQUIRED}
         * 当前存在事务就使用当前事务，当前不存在事务就创建一个新的事务
         * */
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setTimeout(TX_METHOD_TIMEOUT);
        Map<String, TransactionAttribute> txMap = new HashMap<>(10);

        /*添加事务自动代理 前缀名匹配*/
        /*增删改 匹配变更事务*/
        txMap.put("add*", requiredTx);
        txMap.put("save*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("create*", requiredTx);
        txMap.put("edit*", requiredTx);
        txMap.put("update*", requiredTx);
        txMap.put("delete*", requiredTx);

        /*查询 匹配只读事务*/
        txMap.put("get*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        txMap.put("find*", readOnlyTx);
        txMap.put("search*", readOnlyTx);
        source.setNameMap(txMap);

        /**
         * 设置事务拦截器
         * 注入配置好的事务管理器 和 事务源表
         */
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        return txAdvice;
    }

    /**
     * @return org.springframework.aop.Advisor
     *
     * @Author junG
     * @Description 为配置好的事务管理器设定拦截范围
     * 生效范围为 {@link TxAdviceConfig.AOP_POINTCUT_EXPRESSION} 设定的范围
     * @Date 15:46 2019/5/1
     * @Param []
     */
    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        log.trace("事务行为处理器切点{}", pointcut.getPointcutExpression());
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}
